package com.hinacom.pix.tls;

import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Enumeration;

/**
 * Created by fyu on 2016/11/7.
 */
public class ClientSocketFactory implements SecureProtocolSocketFactory {
    private static final Log LOG = LogFactory.getLog(ClientSocketFactory.class);
    private SSLContext sslcontext = null;

    private String keyStore;
    private String keyStorePassword;
    private String trustStore;
    private String trustStorePassword;

    public ClientSocketFactory(String trustStore, String trustStorePassword) {
        this.trustStore = trustStore;
        this.trustStorePassword = trustStorePassword;
    }

    public ClientSocketFactory(String keyStore, String keyStorePassword, String trustStore, String trustStorePassword) {
        this.keyStore = keyStore;
        this.keyStorePassword = keyStorePassword;
        this.trustStore = trustStore;
        this.trustStorePassword = trustStorePassword;
    }


    private SSLContext createSSLContext() {
        SSLContext sslcontext = null;
        try {
            KeyManager[] keyManagers = null;
            if(StringUtils.isNotEmpty(this.keyStore)
                    && StringUtils.isNotEmpty(this.keyStorePassword))
            {
                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                if (this.keyStore.endsWith(".p12")) {
                    keyStore = KeyStore.getInstance("pkcs12");
                } else {
                    keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                }

                try (final InputStream is = new FileInputStream(this.keyStore)) {
                    keyStore.load(is, this.keyStorePassword != null ? this.keyStorePassword.toCharArray() : null);
                }
                KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                kmfactory.init(keyStore, this.keyStorePassword != null ? this.keyStorePassword.toCharArray() : null);

                keyManagers = kmfactory.getKeyManagers();
            }

            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            try (final InputStream is = new FileInputStream(this.trustStore)) {
                trustStore.load(is, this.trustStorePassword != null ? this.trustStorePassword.toCharArray() : null);
            }
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(trustStore);

            sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(keyManagers, tmf.getTrustManagers(), (SecureRandom) null);


            Enumeration enumc = trustStore.aliases();
            String keyAlias = null;
            if (enumc.hasMoreElements()) // we are readin just one certificate.
            {
                keyAlias = (String)enumc.nextElement();
            }

            LOG.info("Using TLS");
            Object certObj = trustStore.getCertificate(keyAlias);
            if(certObj instanceof X509CertImpl)
            {
                X509CertImpl cert = (X509CertImpl) trustStore.getCertificate(keyAlias);
                X509CertInfo certInfo = (X509CertInfo)cert.get("x509.info");
                Enumeration<String> elementKeys = certInfo.getElements();

                StringBuffer stringBuffer = new StringBuffer();
                while(elementKeys.hasMoreElements())
                {
                    String elementKey = elementKeys.nextElement();
                    Object elementValue = certInfo.get(elementKey);
                    if(elementValue!=null && elementKey.equalsIgnoreCase("extensions"))
                    {
                        stringBuffer.append(elementValue);
                        stringBuffer.append("\r\n");
                    }
                }
                LOG.info("Trusted certificate:[" + stringBuffer.toString() + "]");
            }

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        }
        return sslcontext;
    }

    private SSLContext getSSLContext() throws IOException {
        if (this.sslcontext == null) {
            this.sslcontext = this.createSSLContext();
        }
        return this.sslcontext;
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException {
        Socket socket = null;
        try {
            socket = this.getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
        } catch (ConnectException var7) {
            LOG.error("Connection was refused when connecting to socket.", var7);
        } catch (IOException var8) {
            LOG.error("I/O problem creating socket.", var8);
        } catch (Exception var9) {
            LOG.error("Problem creating socket.", var9);
        }
        return socket;
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        Socket socket = null;

        try {
            socket = this.getSSLContext().getSocketFactory().createSocket(host, port);
        } catch (ConnectException var5) {
            LOG.error("Connection was refused when connecting to socket.", var5);
        } catch (IOException var6) {
            LOG.error("I/O problem creating socket.", var6);
        } catch (Exception var7) {
            LOG.error("Problem creating socket.", var7);
        }

        return socket;
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        Socket lsocket = null;
        try {
            lsocket = this.getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
        } catch (ConnectException var7) {
            LOG.error("Connection was refused when connecting to socket.", var7);
        } catch (IOException var8) {
            LOG.error("I/O problem creating socket.", var8);
        } catch (Exception var9) {
            LOG.error("Problem creating socket.", var9);
        }
        return lsocket;
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
        Socket lsocket = null;

        try {
            lsocket = this.getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
        } catch (ConnectException var8) {
            LOG.error("Connection was refused when connecting to socket.", var8);
        } catch (IOException var9) {
            LOG.error("I/O problem creating socket.", var9);
        } catch (Exception var10) {
            LOG.error("Problem creating socket.", var10);
        }

        return lsocket;
    }
}
